package com.ruoyi.framework.aspectj

import com.alibaba.fastjson2.JSON
import com.ruoyi.common.enums.HttpMethod
import com.ruoyi.common.filter.PropertyPreExcludeFilter
import com.ruoyi.common.utils.*
import com.ruoyi.common.utils.ip.IpUtils
import com.ruoyi.framework.aspectj.lang.annotation.*
import com.ruoyi.framework.aspectj.lang.enums.BusinessStatus
import com.ruoyi.framework.manager.factory.AsyncFactory
import com.ruoyi.framework.manager.AsyncManager
import com.ruoyi.project.monitor.domain.SysOperLog
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.*
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import org.springframework.validation.BindingResult
import org.springframework.web.multipart.MultipartFile
import org.springframework.web.servlet.HandlerMapping
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

/**
 * 操作日志记录处理
 *
 * @author ruoyi
 */
@Aspect
@Component
class LogAspect {
    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    fun doAfterReturning(joinPoint: JoinPoint, controllerLog: Log, jsonResult: Any?) {
        handleLog(joinPoint, controllerLog, null, jsonResult)
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    fun doAfterThrowing(joinPoint: JoinPoint, controllerLog: Log, e: Exception?) {
        handleLog(joinPoint, controllerLog, e, null)
    }

    protected fun handleLog(joinPoint: JoinPoint, controllerLog: Log, e: Exception?, jsonResult: Any?) {
        try {
            // 获取当前的用户
            val loginUser = SecurityUtils.getLoginUser()

            // *========数据库日志=========*//
            val operLog = SysOperLog()
            operLog.status = BusinessStatus.SUCCESS.ordinal
            // 请求的地址
            val ip = IpUtils.getIpAddr(ServletUtils.request)
            operLog.operIp = ip
            operLog.operUrl = ServletUtils.request.requestURI
            operLog.operName = loginUser.username
            if (e != null) {
                operLog.status = BusinessStatus.FAIL.ordinal
                operLog.errorMsg = StringUtils.substring(e.message, 0, 2000)
            }
            // 设置方法名称
            val className = joinPoint.target.javaClass.name
            val methodName = joinPoint.signature.name
            operLog.method = "$className.$methodName()"
            // 设置请求方式
            operLog.requestMethod = ServletUtils.request.method
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult)
            // 保存数据库
            AsyncManager.me().execute(AsyncFactory.recordOper(operLog))
        } catch (exp: Exception) {
            // 记录本地异常日志
            log.error("==前置通知异常==")
            log.error("异常信息:{}", exp.message)
            exp.printStackTrace()
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log 日志
     * @param operLog 操作日志
     * @throws Exception
     */
    @Throws(Exception::class)
    fun getControllerMethodDescription(joinPoint: JoinPoint, log: Log, operLog: SysOperLog, jsonResult: Any?) {
        // 设置action动作
        operLog.businessType = log.businessType.ordinal
        // 设置标题
        operLog.title = log.title
        // 设置操作人类别
        operLog.operatorType = log.operatorType.ordinal
        // 是否需要保存request，参数和值
        if (log.isSaveRequestData) {
            // 获取参数的信息，传入到数据库中。
            setRequestValue(joinPoint, operLog)
        }
        // 是否需要保存response，参数和值
        if (log.isSaveResponseData && StringUtils.isNotNull(jsonResult)) {
            operLog.jsonResult = StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param operLog 操作日志
     * @throws Exception 异常
     */
    @Throws(Exception::class)
    private fun setRequestValue(joinPoint: JoinPoint, operLog: SysOperLog) {
        val requestMethod = operLog.requestMethod
        if (HttpMethod.PUT.name == requestMethod || HttpMethod.POST.name == requestMethod) {
            val params = argsArrayToString(joinPoint.args)
            operLog.operParam = StringUtils.substring(params, 0, 2000)
        } else {
            val paramsMap =
                ServletUtils.request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE) as Map<*, *>
            operLog.operParam = StringUtils.substring(paramsMap.toString(), 0, 2000)
        }
    }

    /**
     * 参数拼装
     */
    private fun argsArrayToString(paramsArray: Array<Any>?): String {
        var params = ""
        if (!paramsArray.isNullOrEmpty()) {
            paramsArray
                .asSequence()
                .filter { StringUtils.isNotNull(it) && !isFilterObject(it) }
                .forEach {
                    try {
                        val jsonObj = JSON.toJSONString(it, excludePropertyPreFilter())
                        params += "$jsonObj "
                    } catch (_: Exception) {
                    }
                }
        }
        return params.trim()
    }

    /**
     * 忽略敏感属性
     */
    private fun excludePropertyPreFilter(): PropertyPreExcludeFilter {
        return PropertyPreExcludeFilter().addExcludes(*EXCLUDE_PROPERTIES)
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    private fun isFilterObject(o: Any): Boolean {
        val clazz: Class<*> = o.javaClass
        if (clazz.isArray) {
            return clazz.componentType.isAssignableFrom(MultipartFile::class.java)
        } else if (MutableCollection::class.java.isAssignableFrom(clazz)) {
            val collection = o as Collection<*>
            for (value in collection) {
                return value is MultipartFile
            }
        } else if (MutableMap::class.java.isAssignableFrom(clazz)) {
            val map = o as Map<*, *>
            for (value in map.entries) {
                val (_, value1) = value
                return value1 is MultipartFile
            }
        }
        return (o is MultipartFile || o is HttpServletRequest || o is HttpServletResponse
                || o is BindingResult)
    }

    companion object {
        private val log = LoggerFactory.getLogger(LogAspect::class.java)

        /** 排除敏感属性字段  */
        val EXCLUDE_PROPERTIES = arrayOf("password", "oldPassword", "newPassword", "confirmPassword")
    }
}
